--╔══════════════════════════════╗══════════════════════════════════════════════════════════╗
--║  RAIL DECORATION&ACCESORIES  ║ Optional accessory objects that can decorate rails.
--╚══════════════════════════════╝══════════════════════════════════════════════════════════╝
local RAIL = GS_RAILS if RAIL.blockload return end

--Rarely used RNG function. Only use this once per tic, per object, or it'll likely return the exact same result.
RAIL.RNG = function(a, b, s)
	local RAILRNG = (leveltime%3==0) and 15239675 or (leveltime % 2 == 0) and 2347299441 or 313512859
	local ZDELTA = s.GSrailZDelta or s.rollangle or leveltime
	local seg = s.GSsegnum or s.GSrailnum or 5
	RAILRNG = $ * (max(gamemap*9+leveltime, 1) % 128) - (s.x-s.y+s.z) + (ZDELTA-s.frame-s.type+seg) + FixedInt(s.angle)
	RAILRNG = (RAILRNG and $/2 or 1626124580) + (seg*1543) + (ZDELTA*384)

	local final = (RAILRNG >> 4) & (FRACUNIT - 1)
	return ((final * (b - a + 1)) >> FRACBITS) + a
end

--Throw this on top of a rail to dye it's segments!
RAIL.DyeMapThingSpawn = function(s, spn)
	s.flags2 = MF2_DONTDRAW s.fuse = 99
	if (udmf)!=true --Binary map version.
		if (spn.options & MTF_EXTRA) --Effect 1. Forcibly colorize!
			s.colorized = true
		end	
		if (spn.options & MTF_OBJECTSPECIAL) --Effect 4. Sparkling rail!
			s.GSsparklingrail = true
		end
		if (spn.options & MTF_AMBUSH) --Effect 8. Rainbow rail!
			s.GSrainbowrail = true
		end	
		if not spn.angle or (spn.angle > #skincolors-1)
			RAIL.error("\x85"+"ERROR:\x80 One of your rail dye objects selected a non-existent color number:\x82 "+spn.angle+"\n"+
			"\x85"+"If you're using a non-vanilla custom color, assure the mod is added, then reload the map!") return --
		end
		s.GSraildye = spn.angle
		s.color = spn.angle
	else --UDMF version.
		//$arg0 [1-158]Rail Color
		//$arg1 [1]Forcibly Colorize
		//$arg2 [1]Sparkling Rail
		//$arg3 [1]Rainbow Rail
		if spn.args[1] --Forcibly colorize?
			s.colorized = true
		end
		if spn.args[2] --Make a rail jump ramp hologram!
			s.GSsparklingrail = true
		end			
		if spn.args[3] --Floorsprite or papersprite?
			s.GSrainbowrail = true
		end
		--Dye the rail a custom color. Go after everything else!
		if spn.args[0] 
			if (spn.args[0] > #skincolors-1)
				RAIL.error("\x85"+"ERROR:\x80 One of your rail dye objects selected a non-existent color number:\x82 "+spn.args[0])
				RAIL.error("\x85"+"If you're using a non-vanilla custom color, assure the mod is added, then reload the map!")
				return --
			end
			s.color = spn.args[0]
			s.GSraildye = spn.args[0]
		end		
	end
end

--The raildye objects color nearby rails, and can give special properties.
--addHook("MobjThinker", function(s)
RAIL.DyeThinker = function(s)
	if not (type(s)=="userdata" and userdataType(s)=="mobj_t" and s.valid) return end --
	if not s.spawnstart
		s.fuse = 4
		s.spawnstart = true
		if s.GSraildye
			if (s.GSraildye > #skincolors-1)
				RAIL.error("\x85"+"ERROR:\x80 One of your rail dye objects selected a non-existent color number:\x82 "+spn.args[0])
				RAIL.error("\x85"+"If you're using a non-vanilla custom color, ensure the mod is added, then reload the map!")
			else
				s.color = s.GSraildye
			end
		end
	end
	if s.GSmyrail and s.GSmyrail.valid
		local rail = s.GSmyrail 
		for i = 0,FRACUNIT --Take on some properties from attached objects! 
			rail.color = s.GSraildye
			rail.colorized = s.colorized
			rail.GSraildye = s.GSraildye
			if rail.GSrailsideL
			local paper = rail.GSrailsideL
				paper.colorized = s.colorized
				paper.GSraildye = s.GSraildye
				paper.color = s.GSraildye						
			end			
			if rail.GSsegments
				for _,seg in pairs(rail.GSsegments)
					if seg and seg.valid
						seg.GSraildye = s.GSraildye
						seg.color = s.GSraildye
						seg.colorized = s.colorized
						if seg.GSrailsideL
							local paper = seg.GSrailsideL
							paper.colorized = s.colorized
							paper.GSraildye = s.GSraildye
							paper.color = s.GSraildye						
						end
					end
				end
			end
			if not (rail.GSnextrail and rail.GSnextrail.valid) --RAIL.print("Dye: Ended at "+i) 
				break --
			end
			rail = rail.GSnextrail
		end
	end
end

--A light to help see rails! Or just to put down a light anywhere for fun. Can be colored, scaled, and attached to rails!
RAIL.LightMapThingSpawn = function(s, spn)
	s.blendmode = AST_ADD
	s.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS

	local scalemult = 100
	if (udmf)!=true --Binary map options
		if spn
			if (spn.options & MTF_EXTRA) --Effect 1. 0.5x size!
				scalemult = max($-50,50)
			end
			if (spn.options & MTF_OBJECTFLIP) --Effect 2! 0.25x size!
				scalemult = max($-75,25)
			end
			if (spn.options & MTF_OBJECTSPECIAL) --Effect 4! Line of 3 Rail Lights that attaches to a nearby rail.
				s.GSlineoflights = true
			end
			if (spn.options & MTF_AMBUSH) --Effect 8. Make it huge!
				scalemult = 200
				if (spn.options & MTF_EXTRA)
					scalemult = $+200
				end
				if (spn.options & MTF_OBJECTFLIP)
					scalemult = $+200
				end
			end
			if spn.extrainfo --Obtuse way to add ramps to binary too.
			--	print("\x89"+"spn.extrainfo\x80 exists:"+spn.extrainfo)
				if spn.extrainfo == 6
					s.renderflags = $|RF_PAPERSPRITE
				elseif (spn.extrainfo > 6)
					s.GSlighttype = 1
					if spn.extrainfo == 9
						s.renderflags = $|RF_PAPERSPRITE
					else
						s.renderflags = $|RF_SLOPESPLAT|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD	
					end
					s.color = SKINCOLOR_EMERALD
				end
			end
		end
		
		--Change color?
		if not spn.angle or (spn.angle > #skincolors-1)
			s.color = SKINCOLOR_YELLOW
		else
			s.color = spn.angle
		end		
		local scale = s.scale or FRACUNIT --Change scale?
		if scalemult != 100
			s.scale = FixedDiv(FixedMul(scale, scalemult), 100)
		end
	elseif spn and spn.args!=nil --UDMF options
	
		local ARG = spn.args
		//$arg0 [1-158]Light's Color
		//$arg1 [1]Attach 8 Lights to Rail
		//$arg2 [1]Jump-Ramp Light
		//$arg3 [1-2]Splat/Paper
		
		if ARG[1] --Attach lights?
			s.GSlineoflights = true
		end
		if ARG[2] --Make a rail jump ramp hologram!
			s.GSlighttype = 1
			if not (ARG[3]==2) --If we didn't force it to be a papersprite, then...
				if udmf==true and ARG[4]
					local TILT = ARG[4] if (TILT>99) TILT = $ & ~16384 end
					s.GSlightpitch = TILT
				end
				if not s.GSlightpitch and spn.pitch
					s.GSlightpitch = spn.pitch
				end
				if (s.GSlightpitch != 90)
					s.renderflags = ($|RF_SLOPESPLAT|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD) & ~RF_PAPERSPRITE
				end
			end
			s.color = SKINCOLOR_EMERALD
		end
		--Floorsprite or papersprite?	
		if ARG[3]
			if ARG[3] == 1
				s.renderflags = ($|RF_SLOPESPLAT|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD) & ~RF_PAPERSPRITE
			elseif ARG[3] == 2 or ARG[2]==1 and not (s.GSlightpitch and s.GSlightpitch!=90)
				s.renderflags = ($|RF_PAPERSPRITE) & ~(RF_FLOORSPRITE|RF_NOSPLATBILLBOARD|RF_SLOPESPLAT)			
			end
		end
		--Custom Light Color. Go after everything else.
		if (ARG[0]) and not (ARG[0] > #skincolors-1)
			if ARG[2]==1
				s.colorized = true
			end
			s.color = spn.args[0]
		elseif not (ARG[2])
			s.color = SKINCOLOR_YELLOW
		end
	end
end

--Rainbow rail colors are ordered like this!
local GSRRBCOLORS = {
SKINCOLOR_SUPERGOLD5,
SKINCOLOR_SUPERGOLD4,
SKINCOLOR_SUPERGOLD3,
SKINCOLOR_SUPERGOLD2,
SKINCOLOR_SUPERGOLD1,
SKINCOLOR_SUPERTAN1,
SKINCOLOR_SUPERTAN2,
SKINCOLOR_SUPERTAN3,
SKINCOLOR_SUPERTAN4,
SKINCOLOR_SUPERTAN5,
SKINCOLOR_SUPERPERIDOT5,
SKINCOLOR_SUPERPERIDOT4,
SKINCOLOR_SUPERPERIDOT3,
SKINCOLOR_SUPERPERIDOT2,
SKINCOLOR_SUPERPERIDOT1,
SKINCOLOR_SUPERSKY1,
SKINCOLOR_SUPERSKY2,
SKINCOLOR_SUPERSKY3,
SKINCOLOR_SUPERSKY4,
SKINCOLOR_SUPERSKY5,
SKINCOLOR_SUPERPURPLE5,
SKINCOLOR_SUPERPURPLE4,
SKINCOLOR_SUPERPURPLE3,
SKINCOLOR_SUPERPURPLE2,
SKINCOLOR_SUPERPURPLE1,
SKINCOLOR_SUPERRED5,
SKINCOLOR_SUPERRED4,
SKINCOLOR_SUPERRED3,
SKINCOLOR_SUPERRED2,
SKINCOLOR_SUPERRED1,
SKINCOLOR_SUPERORANGE1,
SKINCOLOR_SUPERORANGE2,
SKINCOLOR_SUPERORANGE3,
SKINCOLOR_SUPERORANGE4,
SKINCOLOR_SUPERORANGE5,
SKINCOLOR_SUPERGOLD5
}
--Track a particular rail?
RAIL.RailTracker = function(s) if not (s and s.valid) return end --
	local rail = s.GSmyrail
	if not (rail and rail.valid)
	or not rail.GSsegments
	or not rail.GSsparklingrail and not rail.GSrainbowrail
		P_RemoveMobj(s) return --
	end
	s.tics,s.fuse = 3,3
	s.sprite,s.frame = SPR_NULL,A
	s.flags = MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY
	s.flags2 = MF2_DONTDRAW

	local NEWCOLOR
	if rail.GSrainbowrail and leveltime%3==0
		if rail.GSsparklingrail or not (rail.GSinvisiblerail or rail.sprite == SPR_NULL or (rail.flags2&MF2_DONTDRAW))
			local MAXCOLOR = (#GSRRBCOLORS)-1
			for i = 1,MAXCOLOR
				if (i >= MAXCOLOR)
					NEWCOLOR = SKINCOLOR_SUPERGOLD5 break --
				elseif rail.color == GSRRBCOLORS[i]
					NEWCOLOR = $+1
				end
			end
			rail.color = NEWCOLOR or SKINCOLOR_SUPERGOLD5
			rail.GSraildye = rail.color
			NEWCOLOR = rail.color
		end
	end
	local SEGSTABLE = rail.GSsegments
	for i = 1,#SEGSTABLE -- Yes, I do know what I'm writing. No, I'm not taking it back.
		local seg = SEGSTABLE[i]
		if not (seg and seg.valid) continue end --
		if NEWCOLOR
			seg.color,seg.GSraildye = NEWCOLOR,NEWCOLOR
			local L,R = seg.GSrailsideL, seg.GSrailsideR
			if L and L.valid L.color,L.GSraildye = NEWCOLOR,NEWCOLOR end
			if R and R.valid R.color,R.GSraildye = NEWCOLOR,NEWCOLOR end
		end
		if rail.GSsparklingrail
			local RNG,RNG2,RNGZ = RAIL.RNG(-120, 120, s), RAIL.RNG(119, -119, s), RAIL.RNG(-22, 45, s)
			if (leveltime%2) RNG,RNG2 = $*-1,$*-1 end
			
			local S = P_SpawnMobjFromMobj(s, RNG*s.scale, RNG2*s.scale, RNGZ*s.scale, MT_GSRAILGFX)
			if S and S.valid
				S.sprite,S.frame = SPR_NSPK, (leveltime%3==0) and B or A
				S.tics,S.fuse = 99,9
				S.sttfade,S.sttfinalframe = true,(S.frame&FF_FRAMEMASK)
				S.spriteyscale, S.spritexscale = FRACUNIT+RAIL.RNG(-20000, 20000, s), S.spriteyscale
				S.color,S.colorized = (leveltime%2) and seg.color or SKINCOLOR_SILVER, true
				S.destscale,S.scalespeed = $%2*99, $%2*2
			end
		end
	end
end

--Be an ANIMATED light!
RAIL.LightThinker = function(s)
	if s.RAILTRACKER RAIL.RailTracker(s) return end -- This is not a light, but a tracker!
	s.spawnlife = s.spawnlife and $+1 or 1	
	if not (s.spawnlife > 5) return end --Delay the start of the setup and behaviour.
	
	local rail = s.GSmyrail if rail==nil rail = s.GSgrind and s.GSgrind.myrail end
	
	if s.minTRANS == nil --Set translucency values.
		local FACEANG = s.angle
		s.minTRANS = (s.GSlighttype==1) and FF_TRANS10 or FF_TRANS30
		s.maxTRANS = (s.GSlighttype==1) and FF_TRANS60 or FF_TRANS80		
		if rail and rail.valid
			FACEANG = RAIL.ang(rail)
		end
		s.angle = FACEANG+ANGLE_90
		if s.GSlighttype == 1 
			if (s.renderflags & RF_FLOORSPRITE) and (s.GSlightpitch!=90) --Ramp jump light!
				P_CreateFloorSpriteSlope(s)
				s.floorspriteslope.o = {x = s.x, y = s.y, z = s.z}
				s.floorspriteslope.xydirection = FACEANG+ANGLE_90
				if s.GSlightpitch
					s.floorspriteslope.zangle = s.GSlightpitch*ANG1
				else
					s.floorspriteslope.zangle = ANG30 --Slightly slanted for better readability!
				end
			else
				s.spriteyoffset = $+(37<<16)
			end
		end
	end
	
	if s.addtract == nil --Setup scale and flip values.
		s.scalespeed = max(1, (s.scale/51))
		s.eflags = $ & ~MFE_VERTICALFLIP
		s.flags2 = $ & ~MF2_OBJECTFLIP
		s.addtract = FF_TRANS10
		s.maxsize = s.scale
		s.minsize = max(1, s.scale-(s.scale / 10))
	else
		--Flicker in opacity a little.
		if s.spawnlife % 3 == 0 -- Only change translucency every 3rd frame.
			local TRANS = (s.frame & FF_TRANSMASK)
			if (TRANS >= s.maxTRANS)
				s.addtract = -FF_TRANS10
			elseif (TRANS < s.minTRANS)
				s.addtract = FF_TRANS10 --FF_TRANS10 is the exact same as 1 FRACUNIT!  TRANS30 is FRACUNIT*3, etc!
			end
			s.frame = (s.frame & ~FF_TRANSMASK) | min(FF_TRANS90, TRANS+s.addtract)		
		end
		
		--Animated jumpramp light behaviour!
		if s.GSlighttype == 1 --Animated jumpramp light!
			local FRAME = (s.frame & FF_FRAMEMASK)
			if (FRAME < C) or (FRAME >= N)
				FRAME = C
			else
				FRAME = $+1
			end
			s.frame = (s.frame & ~FF_FRAMEMASK)|FRAME
		else --Animate the scale a bit as "flickering".
			s.destscale = (s.scale>=s.maxsize) and s.minsize or (s.scale<=s.minsize) and s.maxsize or $
		end
		
		--Attach to rails and place a line of adjacent lights you have the Special Flag.
		if s.GSlineoflights and rail and rail.valid and rail.GSrailXYlength!=nil
			local LENGTH = rail.GSrailXYlength
			local SCALE = max(1<<16, s.scale)
			local SPRITEYOFFSET = -3<<16
			local ANG = rail.angle+ANGLE_90
			if (s.GSlighttype==1)
				ANG = rail.angle
				if (s.renderflags & (RF_FLOORSPRITE|RF_SLOPESPLAT)) 
					if (s.z < s.floorz+(32<<16)) --Ramp jump light!
						s.z = $+(24<<16)
					end			
					SPRITEYOFFSET = 0
				else
					SPRITEYOFFSET = 39<<16
				end
			end
			for d = -1,1,2
				local DIRECTION = (s.GSlighttype ~= 2) and d or 0
				for i = 0, 3
					local DIST = (i == 2) and LENGTH / 2 or (i == 3) and LENGTH - (LENGTH / 4) or LENGTH / 4
					local RX = (i ~= 0) and (rail.x - FixedMul(DIST, cos(rail.angle))) or rail.x
					local RY = (i ~= 0) and (rail.y - FixedMul(DIST, sin(rail.angle))) or rail.y

					local ghs = P_SpawnMobjFromMobj(s,0,0,0,MT_GSRAILLIGHT)
					if ghs and ghs.valid
						ghs.blendmode = AST_ADD
						ghs.color, ghs.colorized = s.color,s.colorized
						ghs.renderflags = s.renderflags
						ghs.addtract = s.addtract
						ghs.frame = s.frame
						if SPRITEYOFFSET
							ghs.spriteyoffset = $+SPRITEYOFFSET
						end
						ghs.minsize, ghs.maxsize = s.minsize,s.maxsize
						ghs.scalespeed = s.scalespeed
						ghs.GSlightnum = i
						ghs.tracer = s
						ghs.spawnlife = 9
						ghs.GSlighttype = s.GSlighttype
	
						P_MoveOrigin(ghs, 
						RX + FixedMul(49*SCALE*DIRECTION, cos(ANG)),
						RY + FixedMul(49*SCALE*DIRECTION, sin(ANG)), ghs.z)
						ghs.z = RAIL.GetRailZ(rail, ghs, 0) + ((s.GSlighttype==1) and 32*SCALE or -18*SCALE)
					end
				end
				if s.GSlighttype == 1 break end --Only need 1!
			end
			P_RemoveMobj(s) return --
		end	
	end
end

--Hit box for rail-attached accessory objects, forcing them to behave differently.
--addHook("MobjThinker", function(s)
RAIL.AttachedThingThinker = function(s) if not (s.valid) return end --
--	if not (type(s)=="userdata" and userdataType(s)=="mobj_t" and s.valid) return end  --
	local t = s.tracer
	if not (t and t.valid) P_RemoveMobj(s) return end --
	
	s.XS_DontSnapToMe = true
	t.XS_DontSnapToMe = true
	t.flags = $|MF_NOCLIPTHING --Make the object non-interactable and use a custom hitbox to mimick behaviour.
	
	if (t.flags & MF_MONITOR) 
		if t.health --I add MF_SPRING so players can home into a Monitor when it's actually a rail object.
			s.flags = $|MF_SPRING & ~MF_SOLID
		else
			s.flags = $ & ~MF_SPRING --Stop being a homing target if the monitor is destroyed!
		end
	end
end

--A pole for immersion, something Sonic Frontiers lacks in spades.
RAIL.PoleMapThingSpawn = function(s, spn)
	local scalemult = 100
	if (udmf)!=true --Binary map options
		if spn
			if (spn.options & MTF_EXTRA) --Effect 1. 0.5x size!
				scalemult = max($-50,50)
			end
			if (spn.options & MTF_OBJECTFLIP) --Effect 2! 0.25x size!
				scalemult = max($-75,25)
			end
			if (spn.options & MTF_OBJECTSPECIAL) --Effect 4! Line of 3 Rail Lights that attaches to a nearby rail.
				s.GSlineoflights = true
			end
			if (spn.options & MTF_AMBUSH) --Effect 8. Make it huge!
				scalemult = 200
				if (spn.options & MTF_EXTRA)
					scalemult = $+200
				end
				if (spn.options & MTF_OBJECTFLIP)
					scalemult = $+200
				end
			end
			if spn.extrainfo --Obtuse way to add ramps to binary too.
				if spn.extrainfo == 6
					s.renderflags = $|RF_PAPERSPRITE
				elseif (spn.extrainfo > 6)
					s.GSlighttype = 1
					if spn.extrainfo == 9
						s.renderflags = $|RF_PAPERSPRITE
					else
						s.renderflags = $|RF_SLOPESPLAT|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD	
					end
					s.color = SKINCOLOR_EMERALD
				end
			end
		end
		
		--Change color?
		if not spn.angle or (spn.angle > #skincolors-1)
			s.color = SKINCOLOR_YELLOW
		else
			s.color = spn.angle
		end		
		local scale = s.scale or FRACUNIT --Change scale?
		if scalemult != 100
			s.scale = FixedDiv(FixedMul(scale, scalemult), 100)
		end
	elseif spn and spn.args!=nil --UDMF options
		local ARG = spn.args
		//$arg0 [1-158]Pole Color
		//$arg1 [1]From Ceiling
		//$arg2 [1-9]Translucent/glass pole
		//$arg3 [1]Solid (For whatever reason...)
		if ARG[1]==1 --Attach from the ceiling
			local FLOORDIST = abs(s.z-s.floorz)
			s.eflags = $|MFE_VERTICALFLIP
			s.flags2 = $|MF2_OBJECTFLIP
			s.z = s.ceilingz-FLOORDIST
		end
		if (ARG[2]) and ((ARG[2]) < 10) --Make it a translucent pole!
			s.GSglasspole = ARG[2]<<16
			s.frame = ($ & ~FF_TRANSMASK)|(s.GSglasspole)
		end
		if (ARG[0]) and not (ARG[0] > #skincolors-1) --Custom Color!
			s.color = ARG[0]
		else
			s.color = SKINCOLOR_RED
		end
		--Floorsprite or papersprite?	
		if ARG[3] == 1
			s.flags = $|MF_SOLID & ~(MF_SCENERY|MF_NOBLOCKMAP|MF_NOCLIPTHING)		
		end		
	end
end